/*
 * Copyright (c) 2009 Project E7
 *
 * This file is part of ZebBowling
 *
 * ZebBowling is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ZebBowling is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ZebBowling. If not, see <http://www.gnu.org/licenses/>.
 **/

package org.zebbowling.control;

import com.sun.spot.io.j2me.radiogram.Radiogram;
import com.sun.spot.util.Utils;
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.Arrays;
import org.zebbowling.world.BowlingGameState;
import org.zebbowling.world.BowlingPlayer;
import org.zebbowling.world.GameStates;
import org.zebbowling.world.SpotBowlingLane;

/**
 * This controller is to be used in Zebulon Bowling.
 *
 * @author Joel Jauregui
 */
public class ZBowlingController extends Controller {
    
    public static final int THRESHOLD = 40;
    public static final int MULTIPLIER = (int) (100 / 9);

    private SpotBowlingLane sbl;
    private BowlingPlayer player;
    private Robot r;
    private byte[] sws;
    private double[] tiltAngles;
    private double[] accelData;
    private int mag, curve, last, state, lastState, controlNum;

    protected ZBowlingController(SpotBowlingLane spl) {
        super();
        this.sbl = spl;
        state = GameStates.MAIN_MENU;
        controlNum = mag = curve = 0;
        last = -1337;
        tiltAngles = new double[] {0,0,90};//Assume it is in intial position
        sws = new byte[] {0,0};//Actually represents booleans
                               //Assume neither switch is pressed
        accelData = new double[3];
        try {
            r = new Robot();
            }
        catch(AWTException e) {/*ignore*/}
        }

    /**
     * Creates a <tt>ZBowlingController</tt> with <tt>num</tt> being is controller
     * port number.
     * <br>NOTE: It is recommended to use the static method to create controllers
     * since an array is needed for
     * {@link ControllerBaseStation#setControlHandlers(org.zebbowling.control.ControlHandler[])}
     * if you plan on using multiple controllers.</br>
     *
     * @param spl The SpotBowlingLane to that the controller will talk to
     * @param num The controller port number of this controller
     */
    public ZBowlingController(SpotBowlingLane spl, int num) {
        this(spl);
        controlNum = num;
//        spl.getPlayer(controlNum - 1);
        if(num != 1) {
            threadSuspended = true;
            state = GameStates.IN_GAME_WAITING_A;
            }
        }

    /**
     * Factory method to create an array of <tt>ZBowlingController</tt>s up to
     * the max value.
     *
     * @param spl The {@link SpotBowlingLane} to be associated with the controllers
     * @param max The amount of controllers to create
     * @return An array with all the controllers created
     */
    public static ZBowlingController[] createControllers(SpotBowlingLane spl, int max) {
        ZBowlingController[] zbcs = new ZBowlingController[max];
        for (int i = 0; i < zbcs.length; i++)
            zbcs[i] = new ZBowlingController(spl, i + 1);
        return zbcs;
        }

    public void handleInput(Radiogram pkt) {
        if(checkChanges(pkt) && !threadSuspended) {
            switch(gameState()) {
                case GameStates.IN_GAME_WAITING_A:
                    if(lastState == GameStates.MAIN_MENU) {
                        player = sbl.getPlayer(controlNum - 1);
                        lastState = state;
                        }
                    if(byteToBool(sws[0])) {
                        lastState = GameStates.IN_GAME_WAITING_A;
                        state = GameStates.SUB_MENU;
                        sbl.pauseGame();
                        }
                    if(byteToBool(sws[1]))
                        state = GameStates.IN_GAME_WAITING_RELEASE;
                    break;
                case GameStates.IN_GAME_WAITING_RELEASE:
                    if(!byteToBool(sws[1])) {
                        setMagCurve(pkt);
                        sbl.setPowerCurve(mag, curve);
                        state = GameStates.IN_GAME_WAITING_A;
                        }
                    break;
                case GameStates.SUB_MENU:
                    if(byteToBool(sws[0])) {
                        state = lastState;
                        sbl.pauseGame();
                        }
                    break;
                case GameStates.MAIN_MENU:
                    lastState = state;
                    if(byteToBool(sws[1])) {
                        sbl.startGame();
                        state = GameStates.IN_GAME_WAITING_A;
                        }
                    if(tiltAngles[1] < -THRESHOLD) {
                        r.keyPress(KeyEvent.VK_UP);
                        last = KeyEvent.VK_UP;
                        }
                    if(tiltAngles[1] > THRESHOLD) {
                        r.keyPress(KeyEvent.VK_DOWN);
                        last = KeyEvent.VK_DOWN;
                        }
                    if(last != -1337 && tiltAngles[1] <= THRESHOLD &&
                            tiltAngles[1] >= -THRESHOLD) {
                        r.keyRelease(last);
                        last = -1337;
                        }
                    break;
                default:
                    throw new IllegalStateException(gameState() + " is not a valid" +
                            " value for game state.");
                }
            Utils.sleep(10);
            }        
        }    

    private boolean checkChanges(Radiogram pkt) {
        boolean change = false;
        byte[] newSS = new byte[2];
        double[] newTilt = new double[3];
        try {
            for (int i = 0; i < newSS.length; i++)
                newSS[i] = pkt.readByte();
            for (int i = 0; i < newTilt.length; i++)
                newTilt[i] = Math.toDegrees(pkt.readDouble());
            }
        catch(IOException e) {}
        change = !Arrays.equals(newTilt, tiltAngles) || !Arrays.equals(sws, newSS);
        if(change) {
            sws = newSS;
            tiltAngles = newTilt;
            }
        return change;
        }

    private void setMagCurve(Radiogram pkt) {        
        try {
            for (int i = 0; i < accelData.length; i++)
                accelData[i] = pkt.readDouble();
            }
        catch(Exception e) {e.printStackTrace();}
        mag = Math.abs(Math.min((int) ((accelData[1] + 1) * MULTIPLIER), 99));
        curve = (int) (accelData[0] * MULTIPLIER * 3);
        curve = Math.signum(curve) == 1 ? Math.min(curve, 99): Math.max(curve, -99);
        }

    private int gameState() {
        return state;
        }

    }
